import { CCBoolean, CCInteger, CCString, Component, Enum, _decorator } from 'cc';
import { BrainParameters } from './BrainParameters';
import { BarracudaPolicy, InferenceDevice } from './BarracudaPolicy';
import NNModel from '../../Core/Internals/NNModel';
import { ActionSpec } from '../Actuators/ActionSpec';
import { ActuatorManager } from '../Actuators/ActuatorManager';
import { IPolicy } from './IPolicy';
import { HeuristicPolicy } from './HeuristicPolicy';
import { CCCAgentsError } from '../CCCAgentsError';
import { Academy } from '../Academy';
import { RemotePolicy } from './RemotePolicy';
import { Agent } from '../Agent';
const { ccclass, property, executeInEditMode, menu, executionOrder } = _decorator;

/**
 * @en
 * Defines what type of behavior the Agent will be using
 *
 * @zh
 * 定义 Agent 将使用的行为类型
 */
export enum BehaviorType {
	/**
	 * @en
	 * The Agent will use the remote process for decision making.
	 * if unavailable, will use inference and if no model is provided, will use the heuristic.
	 *
	 * @zh
	 * Agent 将使用远程进程进行决策。
	 * 如果不可用，将使用推理，如果没有提供模型，将使用启发式。
	 */
	Default,

	/**
	 * @en
	 * The Agent will always use its heuristic
	 *
	 * @zh
	 * Agent 将始终使用其启发式(探索式)
	 *
	 */
	HeuristicOnly,

	/**
	 * @en
	 * The Agent will always use inference with the provided neural network model.
	 *
	 * @zh
	 * Agent 将始终使用提供的神经网络模型进行推理
	 */
	InferenceOnly,
}

/**
 * @en
 * Options for controlling how the Agent class is searched for {@link ObservableAttribute}s.
 *
 * @zh
 * 控制如何搜索 Agent 类中的 {@link ObservableAttribute} 的选项
 */
export enum ObservableAttributeOptions {
	/**
	 * @en
	 * All ObservableAttributes on the Agent will be ignored. This is the
	 * default behavior. If there are no  ObservableAttributes on the
	 * Agent, this will result in the fastest initialization time.
	 *
	 * @zh
	 * 将忽略 Agent 上的所有 ObservableAttributes。这是默认行为。
	 * 如果 Agent 上没有 ObservableAttributes，这将导致最快的初始化时间。
	 *
	 */
	Ignore,

	/**
	 * @en
	 * Only members on the declared class will be examined; members that are
	 * inherited are ignored. This is a reasonable tradeoff between
	 * performance and flexibility.
	 *
	 * @zh
	 * 仅检查声明类上的成员；继承的成员将被忽略。这是性能和灵活性之间的合理折衷。
	 *
	 */
	ExcludeInherited,

	/**
	 * @en
	 * All members on the class will be examined. This can lead to slower startup times.
	 *
	 * @zh
	 * 将检查类上的所有成员。这可能会导致更慢的启动时间。
	 */
	ExamineAll,
}

Enum(InferenceDevice);
Enum(BehaviorType);
Enum(ObservableAttributeOptions);
/**
 * @en
 *  A component for setting an {@link Agent} instance's behavior and brain properties.
 *
 * @zh
 * 用于设置 {@link Agent} 实例的行为和大脑属性的组件。
 */

@ccclass('BehaviorParameters')
@menu('ML Agents/Behavior Parameters')
@executionOrder(-1)
export class BehaviorParameters extends Component {


	@property({ serializable: true, visible: true })
	private _behaviorName: string = '';
	get behaviorName(): string {
		return this._behaviorName;
	}

	set behaviorName(value: string) {
		this._behaviorName = value;
		this.updateAgentPolicy();
	}

	@property({ type: BrainParameters, serializable: true, visible: true })
	private _brainParameters: BrainParameters = new BrainParameters();
	get brainParameters(): BrainParameters {
		return this._brainParameters;
	}

	set brainParameters(value: BrainParameters) {
		this._brainParameters = value;
	}

	@property({ type: NNModel, serializable: true, visible: true })
	private _model: NNModel | null = null!;
	get model(): NNModel | null {
		return this._model;
	}

	set model(value: NNModel | null) {
		this._model = value;
		this.updateAgentPolicy();
	}

	@property({ type: InferenceDevice, serializable: true, visible: true })
	private _inferenceDevice: InferenceDevice = InferenceDevice.Default;
	get inferenceDevice(): InferenceDevice {
		return this._inferenceDevice;
	}
	set inferenceDevice(value: InferenceDevice) {
		this._inferenceDevice = value;
	}

	@property({ serializable: true, visible: true })
	private _deterministicInference: boolean = false;
	get deterministicInference(): boolean {
		return this._deterministicInference;
	}
	set deterministicInference(value: boolean) {
		this._deterministicInference = value;
	}

	onPolicyUpdated: (isInHeuristicMode: boolean) => void = null!;

	@property({ type: BehaviorType, serializable: true, visible: true })
	private _behaviorType: BehaviorType = BehaviorType.Default;
	get behaviorType(): BehaviorType {
		return this._behaviorType;
	}
	set behaviorType(value: BehaviorType) {
		this._behaviorType = value;
		this.updateAgentPolicy();
	}

	@property(CCInteger)
	teamId: number = 0;

	/**
	 * @en
	 * Whether or not to use all the actuator components attached to child GameObjects of the agent.
	 * Note that changing this after the Agent has been initialized will not have any effect.
	 * @zh
	 * 是否使用代理的子 GameObject 的所有执行器组件。
	 * 请注意，此属性在代理初始化后更改将不会产生任何效果。
	 */

	private _useChildActuators: boolean = true;
	get useChildActuators(): boolean {
		return this._useChildActuators;
	}
	set useChildActuators(value: boolean) {
		this._useChildActuators = value;
	}

	@property({  serializable: true, visible: true })
	private _useChildSensors: boolean = true;
	get useChildSensors(): boolean {
		return this._useChildSensors;
	}
	set useChildSensors(value: boolean) {
		this._useChildSensors = value;
	}

	@property({ type: ObservableAttributeOptions, serializable: true, visible: true })
	private _observableAttributeHandling: ObservableAttributeOptions = ObservableAttributeOptions.Ignore;
	get observableAttributeHandling(): ObservableAttributeOptions {
		return this._observableAttributeHandling;
	}

	set observableAttributeHandling(value: ObservableAttributeOptions) {
		this._observableAttributeHandling = value;
	}

	get fullyQualifiedBehaviorName(): string {
		return `${this._behaviorName}?team=${this.teamId}`;
	}

	protected onEnable(): void {
		this.updateAgentPolicy = () => {
			this.isInHeuristicMode();
		};

		console.log(this._behaviorName)
	}

	generatePolicy(actionSpec: ActionSpec, actuatorManager: ActuatorManager): IPolicy {
		switch (this._behaviorType) {
			case BehaviorType.HeuristicOnly:
				return new HeuristicPolicy(actuatorManager, actionSpec);
			case BehaviorType.InferenceOnly:
				if (this._model == null) {
					throw new CCCAgentsError(
						`Can't use Behavior Type ${BehaviorType.InferenceOnly} without a model. Either assign a model, or change to a different Behavior Type.`
					);
				}
				return new BarracudaPolicy(
					actionSpec,
					actuatorManager.actuators,
					this._model,
					this._inferenceDevice,
					this._behaviorName,
					this._deterministicInference
				);
			case BehaviorType.Default:
				if (Academy.instance.isCommunicatorOn) {
					return new RemotePolicy(actionSpec, actuatorManager.actuators, this.fullyQualifiedBehaviorName);
				}
				if (this._model != null) {
					return new BarracudaPolicy(
						actionSpec,
						actuatorManager.actuators,
						this._model,
						this._inferenceDevice,
						this._behaviorName,
						this._deterministicInference
					);
				} else {
					return new HeuristicPolicy(actuatorManager, actionSpec);
				}
			default:
				return new HeuristicPolicy(actuatorManager, actionSpec);
		}
	}

	private updateAgentPolicy(): void {
		const agent = this.getComponent(Agent);
		if (agent == null) {
			return;
		}
		agent.reloadPolicy();
		this.onPolicyUpdated?.(this.isInHeuristicMode());
	}

	private isInHeuristicMode(): boolean {
		if (this._behaviorType === BehaviorType.HeuristicOnly) {
			return true;
		}

		return (
			this._behaviorType === BehaviorType.Default &&
			this._model === null &&
			(!Academy.isInitialized || (Academy.isInitialized && !Academy.instance.isCommunicatorOn))
		);
	}
}
